CircleCI と GitHub で AWS SAM のサーバーレスアプリを自動デプロイしてみた (開発環境 & 本番環境)
CircleCIを使って、AWS SAMのデプロイを自動化してみました。
やったことをまとめた結果、ハンズオンみたいな手順書になりました。
なお、デプロイのみを対象とします。(テストは扱いません)
おすすめの方
- AWS SAMを使っている
- AWS SAMを自動デプロイしたい
- CircleCIで自動デプロイしたい
目次
- 環境
- リポジトリとブランチ運用
- AWSにサーバーレスアプリケーションを作成する
- AWS SAMデプロイの準備
- CircleCIの準備
- CircleCIの設定
- CircleCIでデプロイさせる!(開発環境)
- CircleCIでデプロイさせる!(本番環境)
- さいごに
- 参考
環境
項目 | バージョン |
---|---|
macOS | High Mojave 10.14.5 |
AWS CLI | aws-cli/1.16.179 Python/3.6.8 Darwin/18.6.0 botocore/1.12.169 |
AWS SAM CLI | 0.17.0 |
Python | 3.6 |
リポジトリ | GitHub |
リポジトリとブランチ運用
Gitリポジトリは、GitHubフローのような運用とし、Pushされたタグを本番環境にデプロイします。
自動デプロイの対象は、下記とします。デプロイ先は、同じAWSアカウントです。
種類 | 名前の例 | 環境 | AWSスタック名 |
---|---|---|---|
ブランチ | master | 開発環境 | CircleCIDeploySample-dev |
ブランチ | issues/123 | 開発環境 | CircleCIDeploySample-dev |
タグ | v1.2.3 | 本番環境 | CircleCIDeploySample-prod |
AWSにサーバーレスアプリケーションを作成する
作るものは単純なWebAPIです
次のWebAPIを作成します。
Path | Method |
---|---|
/message/{id} | GET |
下記のようなJSONを返します。
{ "message": "Pathで指定したid" }
AWS SAMプロジェクトの準備
下記コマンドでプロジェクト一式を作成します。
$ sam init --runtime python3.6 --name CircleCIDeploySample
Python仮想環境の構築
pyenvとpipenvの導入
導入済みの場合は、次へどうぞ!
pyenvを導入します。
$ brew install pyenv $ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile $ exec $SHELL -l
pipenvを導入します。
$ brew install pipenv $ echo 'eval "$(pipenv --completion)"' >> ~/.bash_profile $ exec $SHELL -l
Python仮想環境の作成
Pythonをインストールします。すでにある場合は次へ。
$ pyenv install 3.6.8
zipimport.ZipImportError: can't decompress data; zlib not available
なエラーが出た場合は、下記をご覧ください。
続いて、仮想環境を作成します。
$ pipenv install --python 3.6.8
仮想環境に入ります。
$ pipenv shell
なお、仮想環境はexit
で終了できます。
必要なライブラリを仮想環境に導入
仮想環境に入った状態で、ライブラリを導入します。
$ pipenv install awscli $ pipenv install aws-sam-cli
SwaggerでAPI Gatewayを定義
swagger.yaml
を作成し、下記とします。
swagger: "2.0" info: description: SwaggerとAPI Gatewayのサンプルです。(CircleCIデプロイ用) version: 1.0.0 title: Swagger Sample for CircleCI deploy tags: - name: Message schemes: - https paths: /message/{id}: get: tags: - Message summary: メッセージ取得 description: メッセージを取得します consumes: - application/json produces: - application/json parameters: - name: id in: path description: 任意のID required: true type: string responses: 200: description: successful operation schema: $ref: "#/definitions/MessageResponse" x-amazon-apigateway-integration: uri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorldFunction.Arn}/invocations passthroughBehavior: when_no_templates httpMethod: POST type: aws_proxy definitions: MessageResponse: type: object required: - message properties: message: type: string
Lambda関数の作成
下記とします。
import json def lambda_handler(event, context): request_id = event['pathParameters']['id'] return { 'statusCode': 200, 'body': json.dumps({ 'message': request_id }), }
requirements.txt
の内容は、空っぽにします。
AWS SAMテンプレートファイルの更新
下記とします。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: CircleCIDeploySample Globals: Function: Timeout: 3 Parameters: SystemEnv: Type: String AllowedValues: - prod - dev Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.6 Events: HelloWorld: Type: Api Properties: Path: /message/{id} Method: get RestApiId: !Ref HelloWorldApi HelloWorldApi: Type: AWS::Serverless::Api Properties: Name: !Sub CircleCIDeploySample-Api-${SystemEnv} StageName: !Sub ${SystemEnv} DefinitionBody: Fn::Transform: Name: AWS::Include Parameters: Location: !Sub s3://cm-fujii-circleci-deploy-sample-bucket${SystemEnv}/swagger.yaml Outputs: HelloWorldApiUrl: Value: !Sub https://${HelloWorldApi}.execute-api.${AWS::Region}.amazonaws.com/${SystemEnv}/message/{id}
AWS SAMデプロイの準備
Makefileの作成
Makefileを作成し、デプロイで使用するコマンドをまとめます。あとで使うコマンド達も記載しています。
BUCKET_NAME := cm-fujii-circleci-deploy-sample-bucket-${SYSTEM_ENV} STACK_NAME := CircleCIDeploySample-${SYSTEM_ENV} create-bucket: aws s3 mb s3://$(BUCKET_NAME) copy-swagger: aws s3 cp swagger.yaml s3://$(BUCKET_NAME)/swagger.yaml build: sam build deploy: sam package \ --output-template-file packaged.yaml \ --s3-bucket $(BUCKET_NAME) sam deploy \ --template-file packaged.yaml \ --stack-name $(STACK_NAME) \ --capabilities CAPABILITY_NAMED_IAM \ --no-fail-on-empty-changeset \ --parameter-overrides SystemEnv=${SYSTEM_ENV} get-apigateway-endpoint: aws cloudformation describe-stacks \ --stack-name $(STACK_NAME) \ --query 'Stacks[].Outputs' create-iam-user-for-circleci: aws cloudformation deploy \ --template-file circleci-iam-user.yaml \ --stack-name ${STACK_NAME}-for-CircleCI-User \ --capabilities CAPABILITY_NAMED_IAM \ --parameter-overrides SystemEnv=${SYSTEM_ENV} create-iam-user-access-key: aws iam create-access-key \ --user-name CircleCIDeploySample-${SYSTEM_ENV}-for-CircleCI-User delete-app: aws cloudformation delete-stack \ --stack-name $(STACK_NAME) delete-iam-user-for-ciecleci: aws cloudformation delete-stack \ --stack-name ${STACK_NAME}-for-CircleCI-User
CircleCIの準備
CircleCI用の設定ファイルを作成
.circleci
ディレクトリを作成し、その中にconfig.yml
を作成します。
$ mkdir .circleci $ touch .circleci/config.yml
続いて、config.yml
ファイルの中身を記述します。
version: 2.1 executors: my-executor: docker: - image: circleci/python:3.6.8 environment: PIPENV_VENV_IN_PROJECT: true working_directory: ~/CircleCIDeploySample commands: restore: steps: - restore_cache: key: CircleCIDeploySample-v3-{{ .Branch }}-{{ checksum "Pipfile.lock" }} save: steps: - save_cache: paths: - ".venv" key: CircleCIDeploySample-v3-{{ .Branch }}-{{ checksum "Pipfile.lock" }} jobs: setup: executor: my-executor steps: - checkout - restore - run: name: install command: | sudo pip install pipenv pipenv install - save build: executor: my-executor parameters: env: type: enum enum: ["prod", "dev"] steps: - checkout - restore - run: name: sam-build command: | source .venv/bin/activate aws --version sam --version echo << parameters.env >> if [ << parameters.env >> = "dev" ]; then export SYSTEM_ENV=<< parameters.env >> export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID_DEV export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY_DEV else export SYSTEM_ENV=<< parameters.env >> export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID_PROD export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY_PROD fi make copy-swagger make build deploy: executor: my-executor parameters: env: type: enum enum: ["prod", "dev"] steps: - checkout - restore - run: name: sam-deploy command: | source .venv/bin/activate aws --version sam --version echo << parameters.env >> if [ << parameters.env >> = "dev" ]; then export SYSTEM_ENV=<< parameters.env >> export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID_DEV export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY_DEV else export SYSTEM_ENV=<< parameters.env >> export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID_PROD export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY_PROD fi make deploy workflows: version: 2.1 release-dev-workflow: jobs: - setup: filters: branches: only: - master - /^issues\/\d+$/ - build: env: dev requires: - setup filters: branches: only: - master - /^issues\/\d+$/ - deploy: env: dev requires: - build filters: branches: only: - master - /^issues\/\d+$/ release-prod-workflow: jobs: - setup: filters: branches: ignore: /.*/ tags: only: - /^v\d+\.\d+\.\d+$/ - build: env: prod requires: - setup filters: branches: ignore: /.*/ tags: only: - /^v\d+\.\d+\.\d+$/ - deploy: env: prod requires: - build filters: branches: ignore: /.*/ tags: only: - /^v\d+\.\d+\.\d+$/
CircleCI用のIAMユーザを作成
Web画面から作成してもよいのですが、せっかくなのでCloudFormationを利用して作成します。
circleci-iam-user.yaml
を作成し、下記とします。
AWSTemplateFormatVersion: 2010-09-09 Description: Create IAM User and Role for CircleCI Parameters: SystemEnv: Type: String Description: SystemEnv AllowedValues: - prod - dev Resources: CircleCIUser: Type: AWS::IAM::User Properties: UserName: !Sub CircleCIDeploySample-${SystemEnv}-for-CircleCI-User ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonS3FullAccess - arn:aws:iam::aws:policy/AmazonAPIGatewayAdministrator - arn:aws:iam::aws:policy/AWSLambdaFullAccess - arn:aws:iam::aws:policy/IAMFullAccess CircleCIPoricy: Type: AWS::IAM::Policy Properties: PolicyName: !Sub lCircleCIDeploySample-${SystemEnv}-for-CircleCI-policy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - "cloudformation:*" Resource: "*" Users: - !Ref CircleCIUser
CircleCI用のIAMユーザ作成を実行します。
$ SYSTEM_ENV=dev make create-iam-user-for-circleci $ SYSTEM_ENV=prod make create-iam-user-for-circleci
アクセスキーIDとシークレットキーの取得
下記コマンドで取得し、メモしておきます。
$ SYSTEM_ENV=dev make create-iam-user-access-key $ SYSTEM_ENV=prod make create-iam-user-access-key
流出しないよう、取扱にご注意ください!
S3バケットの作成
ソースコードデプロイ先のバケットを作成します。
$ SYSTEM_ENV=dev make create-bucket $ SYSTEM_ENV=prod make create-bucket
コミット
ここまでの内容をmasterブランチ
にコミットします。コミット済みの場合は次へ!
$ git add . $ git commit -m "create sample"
リポジトリのPush
リポジトリをGitHubにPushします。
$ git push origin master
CircleCIの設定
ここから先は、Web画面で行います。
ログイン
CircleCIにログインします。
CircleCIのプロジェクトを作成
「ADD PROJECT」を選択し、さきほどGitHubにPushしたリポジトリを選択します。
続いて、「Start building」を選択します。
初めてのジョブが走りますが、環境変数が未設定なので失敗します。
環境変数の設定
プロジェクト一覧の設定マークを押し、設定画面に移ります。
Environment Variables
を選択します。
次の環境変数を追加します。
Name | Value |
---|---|
AWS_ACCESS_KEY_ID_DEV | 取得したAccessKeyId(開発用) |
AWS_ACCESS_KEY_ID_PROD | 取得したAccessKeyId(本番用) |
AWS_SECRET_ACCESS_KEY_DEV | 取得したSecretAccessKey(開発用) |
AWS_SECRET_ACCESS_KEY_PROD | 取得したSecretAccessKey(本番用) |
AWS_DEFAULT_REGION | ap-northeast-1 |
AWS_DEFAULT_OUTPUT | json |
CircleCIでデプロイさせる!(開発環境)
開発環境にデプロイ
さきほど失敗したWorkflowsの「Return」を選択し、そこの「Return from failed」を選択します。
すると、再びデプロイが始まります。
しばらくすると成功します!!! (ブラウザによっては自動更新してくれないため、手動更新してください)
API Gatewayのアドレスを取得する(開発環境)
$ SYSTEM_ENV=dev make get-apigateway-endpoint [ [ { "OutputKey": "HelloWorldApiUrl", "OutputValue": "https://yyyyyy.execute-api.ap-northeast-1.amazonaws.com/dev/message/{id}" } ] ]
APIを叩いてみる(開発環境)
適当に叩きます。
$ curl https://yyyyyy.execute-api.ap-northeast-1.amazonaws.com/dev/message/dev-test-0000 {"message": "dev-test-0000"}
できました!!!
CircleCIでデプロイさせる!(本番環境)
本番用Lambdaを少し修正する
せっかくなので、少しだけLambdaのコードを変えてみます。
import json def lambda_handler(event, context): request_id = event['pathParameters']['id'] return { 'statusCode': 200, 'body': json.dumps({ 'env': 'prod', 'message': request_id }), }
特に意味もないですが、prod
と固定文字列を追加してみました。
本番環境にデプロイ
まずはコミットします。
$ git add . $ git commit -m "modify app.py for prod"
続いて、タグを付けて、そのタグをPushします!
$ git tag v1.0.0 $ git push origin v1.0.0
すると、CircleCIによる本番環境へのデプロイが始まります。
しばらくすると、完了しました!
API Gatewayのアドレスを取得する(本番環境)
$ SYSTEM_ENV=prod make get-apigateway-endpoint [ [ { "OutputKey": "HelloWorldApiUrl", "OutputValue": "https://zzzzzz.execute-api.ap-northeast-1.amazonaws.com/prod/message/{id}" } ] ]
APIを叩いてみる(本番環境)
適当に叩きます。
$ curl https://zzzzzz.execute-api.ap-northeast-1.amazonaws.com/prod/message/hello-world {"env": "prod", "message": "hello-world"}
できました!!!
さいごに
こういった作業は初めてだったので、とても時間が掛かりましたが、良い勉強になりました。
どんどん活用していきます!
参考
- AWS::IAM::User | AWS
- AWS Identity and Access Management によるアクセスの制御 | AWS
- IAM ユーザーのアクセスキーを作成する | AWS
- AWS CLIの環境変数 | AWS
- Welcome to CircleCI Documentation | CircleCI
- CircleCI での Python アプリケーションの設定 | CircleCI
- Pre-Built CircleCI Docker Images | CircleCI
- Configuring CircleCI | CircleCI
- circleci/[email protected] | CircleCI
- Mac上のPython仮想環境をpipenv+pyenvへ移行してみた | Developers.IO
- Pipenvを用いたPythonの環境構築 | Qiita
- CircleCI 2.1 の新機能を使って冗長な config.yml をすっきりさせよう!
- いまさらだけどCircleCIに入門したので分かりやすくまとめてみた | Qiita